#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

using Osh.Dvb;
using Osh.Data;

namespace Osh.Dvb
{
	/// <summary>
	/// Summary description for EpgParser.
	/// </summary>
	public class EpgParser : StreamParser<IEpgStreamSource>
	{
		#region EventTracker Class
		public class EventTracker
		{
			public SortedList _rootList;

			public EventTracker()
			{
				_rootList = new SortedList();
			}

			/// <summary>
			/// Returns true if event is already being tracked, otherwise returns false.
			/// </summary>
			/// <param name="networkId"></param>
			/// <param name="transportStreamId"></param>
			/// <param name="serviceId"></param>
			/// <param name="eventId"></param>
			/// <param name="obj"></param>
			/// <returns></returns>
			public bool TrackEvent(ushort networkId, ushort transportStreamId,
				ushort serviceId, ushort eventId, LanguageCode languageCode, object obj)
			{
				uint ntsid = (uint)((networkId << 16) | transportStreamId);
				uint seid = (uint)((serviceId << 16) | eventId);

				SortedList networkTransportList = null;
				int index = _rootList.IndexOfKey(languageCode);
				if(index == -1)
				{
					networkTransportList = new SortedList();
					_rootList.Add(languageCode, networkTransportList);
				}
				else
					networkTransportList = (SortedList)_rootList.GetByIndex(index);

				SortedList serviceEventList = null;
				index = networkTransportList.IndexOfKey(ntsid);
				if(index == -1)
				{
					serviceEventList = new SortedList();
					networkTransportList.Add(ntsid, serviceEventList);
				}
				else
					serviceEventList = (SortedList)networkTransportList.GetByIndex(index);

				index = serviceEventList.IndexOfKey(seid);
				if(index == -1)
				{
					serviceEventList.Add(seid, obj);
					return false;
				}
			
				serviceEventList.SetByIndex(index, obj);
				return true;
			}

			public void Clear()
			{
				_rootList.Clear();
			}
		}
		#endregion EventTracker Class

		private EventTracker _eventTracker;
		private DataContext _dataContext;
		private bool _tracingEnabled;
		private bool _logEitStream;
		private bool _logEitStreamErrors;
		private string _eitStreamLogPath;
		private string _eitStreamErrorsLogPath;

		public EpgParser() : this(false)
		{
		}

		public EpgParser(bool parseOnly)
		{
			_dataContext = parseOnly ? null : new DataContext();
			_logEitStream = false;
			_logEitStreamErrors = false;

			_tracingEnabled = false;
			_eitStreamLogPath = Path.Combine(Osh.Configuration.Configuration.Current.LogFolder, "eit.log");
			_eitStreamErrorsLogPath = Path.Combine(Osh.Configuration.Configuration.Current.LogFolder, "eit-errors.log");
		}

		public bool LogEitStream
		{
			get { return _logEitStream; }
			set
			{
				if(value)
					EnsureLogFolderExists();
				_logEitStream = value;
			}
		}

		public bool LogEitStreamErrors
		{
			get { return _logEitStreamErrors; }
			set
			{
				if(value)
					EnsureLogFolderExists();
				_logEitStreamErrors = value;
			}
		}

		public bool TracingEnabled
		{
			get { return _tracingEnabled; }
			set { _tracingEnabled = value; }
		}

		protected override void ThreadProc()
		{
			_eventTracker = new EventTracker();
			base.ThreadProc();
		}

		protected override Stream OpenStream(IEpgStreamSource streamSource)
		{
			//PsiSectionStream packetStream = new PsiSectionStream(
			//	new FileStream(@"C:\OSH\Logs\eit.log", FileMode.Open, FileAccess.Read, FileShare.Read));
			return streamSource.OpenEpgStream();
		}

		protected override void ParseStream(Stream stream)
		{
			using(EitReader eitReader = new EitReader((PacketStream)stream))
			{
				while(eitReader.MoveToNextPacket() && !this.StopEvent.WaitOne(0, false))
				{
					if(_logEitStream)
						LogEitStreamPacket(stream as IPacketStream);

					EventInformationSection eis = null;
					try
					{
						eis = eitReader.ReadEventInformationSection();
					}
					catch(Exception e)
					{
						if(_logEitStreamErrors)
							LogEitStreamError(stream as IPacketStream, e);
						OnParsingError(e);
					}

					if(eis != null)
					{
						if(_tracingEnabled)
							TraceEventInformationSection(eis);
						try
						{
							if(_dataContext != null)
								StoreEvents(_eventTracker, eis);
						}
						catch(Exception e)
						{
							OnError(e);
						}
					}
				}
			}
		}

		private void StoreEvents(EventTracker eventTracker, EventInformationSection eis)
		{
			foreach(EitEvent eitEvent in eis.Events)
			{
				foreach(Descriptor descriptor in eitEvent.Descriptors)
				{
					if(descriptor is ShortEventDescriptor)
					{
						ShortEventDescriptor sed = (ShortEventDescriptor)descriptor;
						LanguageCode languageCode = sed.LanguageCode;
						string eventName = sed.EventName;
						
						if(eventName == ".") continue;

						string text = GetExtendedEventDescriptorText(eitEvent, languageCode);
						if(text == null)
							text = sed.Text;

						if(languageCode != 0 && (eventName != null || text != null))
						{
							StoreEvent(eventTracker, eis, eitEvent, languageCode,
								DvbUtil.LanguageCodeToString((LanguageCode)languageCode), eventName, text);
						}
					}
				}
			}
		}

		private void StoreEvent(EventTracker eventTracker, EventInformationSection eis, EitEvent eitEvent,
			LanguageCode languageCode, string language, string eventName, string text)
		{
			bool contains = eventTracker.TrackEvent(eis.OriginalNetworkId, eis.TransportStreamId,
				eis.ServiceId, eitEvent.EventId, languageCode, true);

			if(contains)
				return;

			_dataContext.SaveEpgEvent(eis.TransportStreamId, eis.OriginalNetworkId, eis.ServiceId,
				eitEvent.EventId, eitEvent.StartTime, (int)eitEvent.Duration.TotalSeconds,
				language, eventName, text);

			if(_tracingEnabled)
			{
				Trace.WriteLine(string.Format(
					"Saving Event: nid:{0,4}, tid:{1,6}, sid:{2,6}, eid:{3,4}, lang:{4}, time: {5}, name:{6}",
					eis.OriginalNetworkId, eis.TransportStreamId, eis.ServiceId, eitEvent.EventId,
					language, eitEvent.StartTime, eventName));
			}
		}

		private string GetExtendedEventDescriptorText(EitEvent eitEvent, LanguageCode languageCode)
		{
			foreach(Descriptor descriptor in eitEvent.Descriptors)
			{
				if(descriptor is ExtendedEventDescriptor)
				{
					ExtendedEventDescriptor eed = (ExtendedEventDescriptor)descriptor;
					if(eed.LanguageCode == languageCode)
						return eed.Text;
				}
			}
			return null;
		}

		[Conditional("TRACE")]
		private void TraceEventInformationSection(EventInformationSection eis)
		{
			Trace.WriteLine(string.Format(
				"EIS: tableId:{0,3}, length:{1,4}, serviceId:{2,6}, " + 
				"sectionNumber:{3}, lastSectionNumber:{4}, " + 
				"tid:{5,6}, nid:{6,4}, events:{7,2}",
				eis.TableId, eis.Length, eis.ServiceId,
				eis.SectionNumber, eis.LastSectionNumber,
				eis.TransportStreamId, eis.OriginalNetworkId, eis.Events.Length));

			int eventIndex = 0;
			foreach(EitEvent eitEvent in eis.Events)
			{
				Trace.WriteLine(string.Format("\tEvent[{0}] id={1}, {2}, {3}, descriptors:{4,2}",
					eventIndex++, eitEvent.EventId, eitEvent.StartTime, eitEvent.Duration,
					eitEvent.Descriptors.Length));

				foreach(Descriptor descriptor in eitEvent.Descriptors)
				{
					if(descriptor is ShortEventDescriptor)
					{
						ShortEventDescriptor ed = (ShortEventDescriptor)descriptor;
						Trace.WriteLine(
							string.Format("\t\tShortEventDescriptor: name={0}, text={1}",
							ed.EventName, ed.Text));
					}
					else if(descriptor is ExtendedEventDescriptor)
					{
						ExtendedEventDescriptor ed = (ExtendedEventDescriptor)descriptor;
						Trace.WriteLine(
							string.Format("\t\tExtendedEventDescriptor: {0}/{1}, items={2}, text={3}",
							ed.DescriptorNumber, ed.LastDescriptorNumber, ed.Items.Count, ed.Text));
					}
					else
					{
						Trace.WriteLine(
							string.Format("\t\tDescriptor: tag={0:X}, length={1}",
							descriptor.Tag, descriptor.Length));
					}
				}
			}
		}

		private void EnsureLogFolderExists()
		{
			if(Directory.Exists(Osh.Configuration.Configuration.Current.LogFolder))
				return;

			Directory.CreateDirectory(Osh.Configuration.Configuration.Current.LogFolder);
		}

		private void LogEitStreamPacket(IPacketStream packetStream)
		{
			using(BinaryWriter writer = new BinaryWriter(new FileStream(
					  _eitStreamLogPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)))
			{
				writer.BaseStream.Seek(0, SeekOrigin.End);
				writer.Write(packetStream.PacketBuffer, 0, packetStream.PacketSize);
				writer.Flush();
			}
		}

		private void LogEitStreamError(IPacketStream packetStream, Exception e)
		{
			using(BinaryWriter writer = new BinaryWriter(new FileStream(
					  _eitStreamErrorsLogPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)))
			{
				writer.BaseStream.Seek(0, SeekOrigin.End);
				writer.Write(packetStream.PacketBuffer, 0, packetStream.PacketSize);
				writer.Flush();
			}
		}

	}
}
